(ns packet.types
  (:refer-clojure :exclude [deftype char])
  (:require [clojure.string :as str]
            [clojure.math :as math]
            [packet.bytes :as bs]
            [packet.utils :as u]
            [packet.core :as p]))

(defmacro deftype
  "定义类型，比如如下定义一个类型baud
  (deftype baud [n] (u n with * 300))
  你可以这样使用这个类型
  (baud 1 as field)
  它展开后将变为
  (with-field-name field (with (u 1) * 300))
  注意as之后的内容是由外层宏剥离并处理的"
  [type & forms]
  (let [doc (when (string? (first forms)) (first forms))
        forms (cond-> forms doc rest)
        args (when (vector? (first forms)) (first forms))
        forms (cond-> forms args rest)]
    (if (empty? forms)
      (u/line-error (:line (meta &form)) "内容缺失")
      `(defn ~type ~(or doc "字段类型") ~(or args [])
         ~(if (= (count forms) 1)
            (p/transform (first forms))
            `(p/group ~@forms))))))


;;-----------------------------------------------------

(defn u
  "n字节无符号整数的解析器"
  [n]
  {:pre [(<= n 8)]}
  (if p/*build*
    (p/map-input (p/raw n) u/uint->bytes n) 
    (p/map-value (p/raw n) u/bytes->uint)))

(defn i
  "n字节有符号整数的解析器"
  [n]
  {:pre [(<= n 8)]}
  (if p/*build*
    (p/map-input (p/raw n) u/int->bytes n)
    (p/map-value (p/raw n) u/bytes->int)))

(defn ubcd
  "无符号压缩bcd码，即1个字节包含两位十进制数"
  [n]
  {:pre [(<= n 8)]}
  (if p/*build*
    (p/map-input (p/raw n) u/uint->bcd+ n) 
    (p/map-value (p/raw n) u/bcd+->uint)))

(defn bcd
  "有符号压缩bcd码，即1个字节包含两位十进制数，
  最高字节的最高位为符号位，即最高字节能表示的最大数值为79"
  [n]
  {:pre [(<= n 8)]}
  (if p/*build*
    (p/map-input (p/raw n) u/int->bcd+ n) 
    (p/map-value (p/raw n) u/bcd+->int)))

(deftype d "有符号bcd码表示的十进制类型，coef为系数"
  [n coef]
  (bcd n
    with (fn [v]
           (when (number? v)
             (* v coef)))
    and (fn [v]
          (when (number? v)
            (math/round (/ v coef))))))

(deftype ud "无符号bcd码表示的十进制类型，coef为系数"
  [n coef]
  (ubcd n
    with (fn [v]
           (when (number? v)
             (* v coef)))
    and (fn [v]
          (when (number? v)
            (math/round (/ v coef))))))

(deftype f "IEEE格式浮点数，只能是4字节或8字节长"
  [n]
  (p/raw n
    with (fn [bs]
           )
    and (fn [f]
          )))

(defn char
  "单字符解析器"
  []
  (if p/*build*
    (p/map-input (p/raw 1) byte) 
    (p/map-value (p/raw 1) clojure.core/char)))

(defn s
  "固定长度的字符串"
  [n]
  (if p/*build*
    (p/map-input (p/raw n) (fn [s]
                             (take n (concat (map byte s) (repeat 0))))) 
    (p/map-value (p/raw n) (fn [bs]
                             (->> bs
                                  (take-while #(not= % 0))
                                  (map char)
                                  (apply str))))))


(defn lstr
  "以字符串长度开始的字符串，长度的字节数为n"
  ([] (lstr 1))
  ([n]
   (if p/*build*
     (fn [string env]
       (let [len (count string)
             bs (concat (u/uint->bytes len n)
                        (map byte string))]
         {:value bs :pos (:pos env) :len (+ len n)}))
     (p/flatmap (u n)
                (fn [o]
                  (p/map-value (p/raw (:value o))
                               #(->> %
                                     (map clojure.core/char)
                                     (apply str))))))))

(defn zstr
  "以0字节结尾的字符串"
  []
  (if p/*build*
    (fn [string env]
      (let [bs (conj (mapv byte string) 0)]
        {:value bs
         :pos (:pos env)
         :len (count bs)}))
    (fn [input env]
      (let [pos (:pos env)
            sz (bs/size input)
            len (loop [i pos]
                  (if (>= i sz)
                    (p/fail-result input env "内容不足")
                    (if (zero? (bs/get-byte input i))
                      (- i pos)
                      (recur (inc i)))))]
        {:value (->> (bs/get-bytes input pos len)
                     (map clojure.core/char)
                     (apply str))
         :pos pos
         :len (inc len)}))))

(defn dstr
  "用n字节字符串保存的正负浮点数"
  [n]
  (if p/*build*
    (p/map-input (s n) str) 
    (p/map-value (s n) parse-double)))

(defn datetime
  "以指定格式保存的日期时间，格式的形式为YYMDhms，一个字母对应一个字节中的两个数字"
  [format-string]
  {:pre [(every? (set "YMDhms") format-string)]}
  (let [len (count format-string)
        ts (mapv (comp keyword str) format-string)
        ;;四位年份用两个Y表示
        ny (count (filter #(= % :Y) ts))]
    (if p/*build*
      (p/map-input (p/raw len)
                   (fn [date]
                     (map-indexed (fn [i t]
                                    (u/byte->bcd (case t
                                                   :Y (let [y (+ (.getYear date) 1900)]
                                                        (if (and (= ny 2)
                                                                 (or (zero? i)
                                                                     (= i (dec len))))
                                                          (quot y 100)
                                                          (rem y 100)))
                                                   :M (inc (.getMonth date))
                                                   :D (.getDate date)
                                                   :h (.getHours date)
                                                   :m (.getMinutes date)
                                                   :s (.getSeconds date))))
                                  ts)))
      (p/map-value (p/raw len)
                   (fn [bs]
                     (let [m (->> (map (fn [t b] {t (u/bcd->byte b)}) ts bs)
                                  (apply merge-with (if (str/starts-with? format-string "YY")
                                                      (fn [a b] (+ (* a 100) b))
                                                      (fn [a b] (+ (* b 100) a)))))
                           y (or (:Y m) 0)]
                       (java.util.Date. (if (= ny 1) (+ y 100) (- y 1900))
                                        (or (dec (:M m)) 0)
                                        (or (:D m) 1)
                                        (or (:h m) 0)
                                        (or (:m m) 0)
                                        (or (:s m) 0))))))))

(defn ipaddr
  "4字节的ip地址"
  []
  (if p/*build*
    (p/map-input (p/raw 4)
                 (fn [ip]
                   (if ip
                     (->> (str/split (.getHostAddress ip) #"\.")
                          (map parse-long)
                          (map unchecked-byte))
                     (repeat 4 0)))) 
    (p/map-value (p/raw 4)
                 (fn [bs]
                   (java.net.InetAddress/getByAddress (byte-array bs))))))

;;ip字面量的形式为: #ip "192.168.0.1"
(defmethod print-method java.net.InetAddress
  [ip ^java.io.Writer w]
  (.write w (str "#ip \"" (.getHostAddress ip) "\"")))

(defmethod print-dup java.net.InetAddress
  [ip ^java.io.Writer w]
  (print-method ip w))

(defn ip-reader
  "ip字面量的读取器"
  [s]
  (->> (str/split s #"\.")
       (map parse-long)
       byte-array
       java.net.InetAddress/getByAddress))

;;以下是用于文本字段的类型

(deftype integer
    (p/token with parse-long and str))

(deftype decimal
    (p/token with parse-double and str))

(deftype regex [re]
  (p/token should #(re-matches re %)))

